local SarfMacros = {
	setup = {
		namedMacros = {};
		unnamedMacros = {};
		baseButtonName = "SarfMacroButton";
		buttonId = 0;
	};
};

local defaultSetup = {
	namedMacros = {};
	unnamedMacros = {};
};

function SarfMacros:Error(msg)
	ChatFrame1:AddMessage(msg, 1, 0, 0);
end

function SarfMacros:RemoveMacro(name)
	local curMacro = nil;
	if ( type(name) ~= "nil" ) and ( self.setup.namedMacros[name] ) then
		curMacro = self.setup.namedMacros[name];
	else
		return;
	end
	if ( type(curMacro.button) ~= "nil" ) then
		curMacro.button:Disable();
		curMacro.button:Hide();
		if ( type(curMacro.keys) == "table" ) then
			for k,v in pairs(curMacro.keys) do
				SetBinding(v);
				table.remove(curMacro.keys,k);
			end
		end
		curMacro.button = nil;
	end
	table.remove(self.setup.namedMacros, name);
end

function SarfMacros:InstantiateMacro(macro, name, key, key2)
	if ( InCombatLockdown() ) then
		-- TODO: add macro data in and schedule macro button creation
		self:Error("INCOMBAT");
		return;
	end
		
	local curMacro = nil;
	if ( type(name) ~= "nil" ) and ( self.setup.namedMacros[name] ) then
		curMacro = self.setup.namedMacros[name];
	else
		curMacro = {};
		curMacro.name = name;
		if ( type(name) ~= "nil" ) then
			self.setup.namedMacros[name] = curMacro;
		else
			table.insert(self.setup.unnamedMacros, curMacro);
		end
	end
	if ( type(curMacro.keys) == "table" ) then
		for k,v in pairs(curMacro.keys) do
			SetBinding(v);
			table.remove(curMacro.keys,k);
		end
	elseif ( type(key) == "string" ) or ( type(key2) == "string" ) then
		curMacro.keys = {};
	end
	if ( type(key) == "string" ) then
		table.insert(curMacro.keys, key);
	end
	if ( type(key2) == "string" ) then
		table.insert(curMacro.keys, key2);
	end
	curMacro.text = macro;
	self:InitiateMacroButton(curMacro);
	self:SaveMacros();
end

function SarfMacros:InitiateMacroButton(macroData)
	if ( InCombatLockdown() ) then
		self:Error("INCOMBAT");
		return;
	end
	if ( type(macroData) ~= "table" ) then
		self:Error("ERRONEOUS_PARAMETER");
		return;
	end
	local button = macroData.button;
	if ( type(button) == "nil" ) then
		button = self:CreateButton(self:GetNextButtonName());
		macroData.button = button;
	end
	ChatFrame1:AddMessage(macroData.name.." - "..type(button));
	button:SetAttribute("macrotext", macroData.text);
	self:PreventRebind();
	for k,v in pairs(macroData.keys) do
		SetBindingClick(v, button:GetName());
	end
	self:AllowRebind();
end

function SarfMacros:IsRebinding()
	return self.setup.causedBindingsUpdate;
end

function SarfMacros:PreventRebind()
	self.setup.causedBindingsUpdate = true;
end

function SarfMacros:AllowRebind()
	self.setup.causedBindingsUpdate = false;
end

function SarfMacros:CreateButton(name)
	local button = CreateFrame("Button", name, nil, "SecureActionButtonTemplate")
	button:SetAttribute("type", "macro");
	button:SetAttribute("macro", false) -- necessary to make it use macrotext	
	button:SetAttribute("macrotext", "")
    button:SetAttribute("unit", nil);
    button:SetAttribute("spell", nil);
    button:SetAttribute("item", nil);
    button:SetAttribute("target-slot", nil);
	button:EnableMouse(true);
	button:EnableKeyboard(true);
	button:RegisterForClicks("LeftButtonUp", "RightButtonUp");
	return button;
end

function SarfMacros:GetNextButtonName()
	self.setup.buttonId = self.setup.buttonId+1;
	local id = self.setup.buttonId;
	return self.setup.baseButtonName..id;
end

function SarfMacros:SaveMacros()
	if ( type(SarfMacrosDB) ~= "table" ) then
		SarfMacrosDB = {};
	end
	SarfMacrosDB.oldNamedMacros = SarfMacrosDB.namedMacros;
	SarfMacrosDB.oldUnamedMacros = SarfMacrosDB.unnamedMacros;

	SarfMacrosDB.namedMacros = {};
	SarfMacrosDB.unnamedMacros = {};
	for k,v in pairs(self.setup.namedMacros) do
		SarfMacrosDB.namedMacros[k] = self:CopyEntry(v);
		v.button = nil;
	end
	for k,v in pairs(self.setup.unnamedMacros) do
		SarfMacrosDB.unnamedMacros[k] = self:CopyEntry(v);
		v.button = nil;
	end
end

function SarfMacros:ADDON_LOADED()
	if ( arg1 == "SarfMacros" ) then
		self:LoadVariables();
	end
end

function SarfMacros:VARIABLES_LOADED()
	self:LoadVariables();
end

function SarfMacros:PLAYER_ENTERING_WORLD()
	self:RebindMacros();
end

function SarfMacros:UPDATE_BINDINGS()
	self:RebindMacros();
end

function SarfMacros:RebindMacros()
	if ( self:IsRebinding() ) then return; end
	self:PreventRebind();
	for a, macroData in pairs(self.setup.namedMacros) do
		if ( type(macroData.button) ~= "nil" ) then
			for k,v in pairs(macroData.keys) do
				if ( macroData.button ) then
					SetBindingClick(v, macroData.button:GetName());
					--ChatFrame1:AddMessage("SetBindingClick("..v..", "..macroData.button:GetName()..");");
				end
			end
		end
	end
	for a, macroData in pairs(self.setup.unnamedMacros) do
		if ( type(macroData.button) ~= "nil" ) then
			for k,v in pairs(macroData.keys) do
				if ( macroData.button ) then
					SetBindingClick(v, macroData.button:GetName());
				end
			end
		end
	end
	self:AllowRebind();
end

-- Recursive copy function.
function SarfMacros:CopyEntry(entry)
	if ( type(entry) == "table" ) then
		local copy = {};
		for k,v in pairs(entry) do
			copy[k] = self:CopyEntry(v);
		end
		return copy;
	else
		return entry;
	end
end

function SarfMacros:CopyTableRecursive(source, dest, overwrite)
	if ( type(source) == "table" ) then
		local copy = dest;
		if ( type(copy) ~= "table" ) then
			copy = {};
		end
		for k,v in pairs(source) do
			if ( overwrite ) or ( type(copy[k]) ~= type(v) ) then
				copy[k] = self:CopyTableRecursive(v, copy[k], overwrite);
			end
		end
		return copy;
	else
		return source;
	end
end

function SarfMacros:LoadVariables()
	if ( type(SarfMacrosDB) ~= "table" ) then
		SarfMacrosDB = {};
		return;
	end

	if ( type(self.setup) ~= "table" ) then self.setup = {}; end

	self.setup = self:CopyTableRecursive(SarfMacrosDB, self.setup, true);
	
	-- Copies default setup values, ensuring type integrity.
	self.setup = self:CopyTableRecursive(defaultSetup, self.setup, false);
	
	for k,v in pairs(self.setup.namedMacros) do
		if (type(v) == "table") then v.button = nil; end
	end
	for k,v in pairs(self.setup.unnamedMacros) do
		if (type(v) == "table") then v.button = nil; end
	end
	
	self:InitiateMacros();
end

function SarfMacros:InitiateMacros()
	for k, v in pairs(self.setup.namedMacros) do
		self:InitiateMacroButton(v);
	end
	for k, v in pairs(self.setup.unnamedMacros) do
		self:InitiateMacroButton(v);
	end
end

function SarfMacros:Init()
	-- TODO: setup slash command support?
	self:LoadVariables();
	local frame = self.setup.frame;
	if ( type(frame) == "nil" ) then
		frame = CreateFrame("Frame");
		self.setup.frame = frame;
		frame:SetScript("OnEvent", function() if ( type(self[event]) == "function" ) then self[event](self) end end);
		frame:RegisterEvent("ADDON_LOADED");
		frame:RegisterEvent("VARIABLES_LOADED");
		--frame:RegisterEvent("UPDATE_BINDINGS");
		frame:RegisterEvent("PLAYER_ENTERING_WORLD");
	end
end

function SarfMacros_GetInstance()
	return SarfMacros;
end

SarfMacros:Init();